package com.huayal.mall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.huayal.mall.product.entity.CategoryBrandRelationEntity;
import com.huayal.mall.product.service.CategoryBrandRelationService;
import com.huayal.mall.product.vo.CateLog2JsonVo;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.huayal.common.utils.PageUtils;
import com.huayal.common.utils.Query;

import com.huayal.mall.product.dao.CategoryDao;
import com.huayal.mall.product.entity.CategoryEntity;
import com.huayal.mall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;

import javax.cache.annotation.CacheRemoveAll;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    RedissonClient redissonClient;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }


    //构建树形
    @Override
    public List<CategoryEntity> queryPageWithTree(Map<String, Object> params) {
        //1，找到所有的数据
        List<CategoryEntity> list = baseMapper.selectList(null);
        //2，根据数据构建树形结构
        //2.1 找到所有大类
        List<CategoryEntity> collect = list.stream().filter(categoryEntity -> categoryEntity.getParentCid() == 0)
                .map(entity -> {
                    //2.2 根据大类找到所有的子类
                    entity.setChildrens(getCategoryChildrens(entity, list));
                    return entity;
                }).sorted((entity1, entity2) -> {
                    return (entity1.getSort() == null ? 0 : entity1.getSort()) - (entity2.getSort() == null ? 0 : entity2.getSort());
                }).collect(Collectors.toList());
        return collect;
    }

    @Override
    public void updateStatusByIds(List<Long> asList) {
        baseMapper.deleteBatchIds(asList);
    }

    @Override
    public  List<Long> getCatIds(Long catelogId) {
        List<Long> longs = new ArrayList<>();

        List<Long> catIdAll = getCatIdAll(catelogId,longs);
        Collections.reverse(catIdAll);
        return catIdAll;
    }

    /**
     *  @CacheEvict,更新数据时同步删除缓存中的数据
     *
     * @param category
     */
//    @CacheEvict(value = {"category"},key = "'getFirstCateGory'")
    //采用组合注解解决清楚多个缓存中的数据
//    @Caching(evict={
//            @CacheEvict(value = "category",key = "'getFirstCateGory'"),
//            @CacheEvict(value = "category",key = "'getCatelog2JSON'")
//    })
    //删除该分区下的所有缓存数据(更加方便!)
    @CacheEvict(value = "category",allEntries = true)
    @Transactional
    @Override
    public void updateDetail(CategoryEntity category) {
        this.updateById(category);

        CategoryBrandRelationEntity entity = new CategoryBrandRelationEntity();
        entity.setCatelogId(category.getCatId());
        entity.setCatelogName(category.getName());
        QueryWrapper<CategoryBrandRelationEntity> wrapper = new QueryWrapper<>();
        wrapper.eq("catelog_id",category.getCatId());
        categoryBrandRelationService.getBaseMapper().update(entity,wrapper);
    }

    /**
     * 查出所有的商品大类（一级类别）
     * @Cacheable({"category","product"})//【含义：查询缓存，如果有则直接返回，如果没有则查询下面的方法返回并把结果缓存下来】
     * @return
     */
//    @Cacheable(value = {"category","product"},key = "'getFirstCateGory'") 写死的方式
//    @Cacheable(value = {"category","product"},key = "{#root.methodName}")//或key = "#root.methodName"都可
    @Cacheable(value = {"category"},key = "{#root.methodName}",sync = true)//sync = true 本地锁可以解决缓存击穿的问题!它的代码和分布式代码是一样.
    @Override
    public List<CategoryEntity> getFirstCateGory() {
//        long start=System.currentTimeMillis();
        System.err.println("查询了数据库...");
        List<CategoryEntity> categoryEntities = this.baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
        //经测试在数据库中给字段parent_cid添加索引，效率有效提高！
//        System.err.println("getFirstCateGory()消耗时间:"+(System.currentTimeMillis()-start));
        return categoryEntities;

    }

    //未优化(查询三次数据库)
//    @Override
    public Map<String, List<CateLog2JsonVo>> getCatelog2JSON_three() {

        List<CategoryEntity> firstCateGory = getFirstCateGory();//一次数据库操作
        Map<String, List<CateLog2JsonVo>> map = firstCateGory.stream().collect(Collectors.toMap(key -> key.getCatId().toString(), value -> {

            List<CategoryEntity> categoryEntities = this.baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", value.getCatId()));//一次数据库操作
            List<CateLog2JsonVo> cateLog2JsonVos = categoryEntities.stream().map(item -> {
                CateLog2JsonVo cateLog2JsonVo = new CateLog2JsonVo();
                cateLog2JsonVo.setCatalog1Id(item.getParentCid().toString());
                cateLog2JsonVo.setId(item.getCatId().toString());
                cateLog2JsonVo.setName(item.getName());

                List<CategoryEntity> categoryEntities1 = this.baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", item.getCatId()));//一次数据库操作
                List<CateLog2JsonVo.Catalog3List> catalog3Lists = categoryEntities1.stream().map(item1 -> {
                    CateLog2JsonVo.Catalog3List catalog3List = new CateLog2JsonVo.Catalog3List();
                    catalog3List.setCatalog2Id(item1.getParentCid().toString());
                    catalog3List.setId(item1.getCatId().toString());
                    catalog3List.setName(item1.getName());
                    return catalog3List;
                }).collect(Collectors.toList());
                cateLog2JsonVo.setCatalog3List(catalog3Lists);

                return cateLog2JsonVo;
            }).collect(Collectors.toList());

            return cateLog2JsonVos;
        }));

        return map;
    }

    //优化后（只查询一次数据库）
    private List<CategoryEntity> getListCategoryEntity( List<CategoryEntity> categoryEntities,Long catId){
        return categoryEntities.stream().filter(item ->  item.getParentCid().equals(catId) ).collect(Collectors.toList());
    }
//    @Override
    public Map<String, List<CateLog2JsonVo>> getCatelog2JSONForDB() {

        System.err.println("----->查询数据库");

        List<CategoryEntity> categoryEntitiesAll = this.baseMapper.selectList(new QueryWrapper<CategoryEntity>());
        List<CategoryEntity> firstCateGory = getListCategoryEntity(categoryEntitiesAll,0l);
        Map<String, List<CateLog2JsonVo>> map = firstCateGory.stream().collect(Collectors.toMap(key -> key.getCatId().toString(), value -> {

            List<CategoryEntity> categoryEntities = getListCategoryEntity(categoryEntitiesAll,value.getCatId());
            List<CateLog2JsonVo> cateLog2JsonVos = categoryEntities.stream().map(item -> {
                CateLog2JsonVo cateLog2JsonVo = new CateLog2JsonVo();
                cateLog2JsonVo.setCatalog1Id(item.getParentCid().toString());
                cateLog2JsonVo.setId(item.getCatId().toString());
                cateLog2JsonVo.setName(item.getName());

                List<CategoryEntity> categoryEntities1 = getListCategoryEntity(categoryEntitiesAll,item.getCatId());
                List<CateLog2JsonVo.Catalog3List> catalog3Lists = categoryEntities1.stream().map(item1 -> {
                    CateLog2JsonVo.Catalog3List catalog3List = new CateLog2JsonVo.Catalog3List();
                    catalog3List.setCatalog2Id(item1.getParentCid().toString());
                    catalog3List.setId(item1.getCatId().toString());
                    catalog3List.setName(item1.getName());
                    return catalog3List;
                }).collect(Collectors.toList());
                cateLog2JsonVo.setCatalog3List(catalog3Lists);

                return cateLog2JsonVo;
            }).collect(Collectors.toList());

            return cateLog2JsonVos;
        }));

        return map;
    }

    //再优化：添加上redis(使用本地锁)
//    @Override
    public Map<String, List<CateLog2JsonVo>> getCatelog2JSON_o2(){
        String key="getCatelog2JSON";
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        String json = ops.get(key);
        if (StringUtils.isEmpty(json)){

            //处理缓存击穿 this代表当前对象，springboot this代表当前对象运行 单个锁
            synchronized (this){ //本地锁单体架构可以约束，但是分布式架构是没法进行约束的
                json = ops.get(key);
                if (StringUtils.isNotEmpty(json)){
                    System.err.println("----->缓存命中1");
                    return JSONObject.parseObject(json, new TypeReference<>() {});
                }

                Map<String, List<CateLog2JsonVo>> catelog2JSONForBaseData = getCatelog2JSONForDB();
                //防止缓存穿透
                if (catelog2JSONForBaseData==null){
                    System.err.println("----->预防缓存穿透");
                    //null值和"" 不允许，只能是{} *
                    ops.set(key,"{}",5, TimeUnit.SECONDS);//5s过期
                }else {

                    json  = JSON.toJSONString(catelog2JSONForBaseData);
//                ops.set(key,json);
                    //防止雪崩
                    ops.set(key,json,new Random().nextInt(10),TimeUnit.HOURS);
//                    ops.set(key,json,10,TimeUnit.MINUTES);
                    return catelog2JSONForBaseData;
                }
            }

        }

        //需要正确使用TypeReference：new TypeReference<>(){}  【后面这里有一个大括号】
        System.err.println("----->缓存命中0");
        return JSONObject.parseObject(json, new TypeReference<>() {});
    }

    //再优化：添加上redis(使用分布式锁)【这类似源代码，可以使用Redisson简化开发过程】
//    @Override
    public Map<String, List<CateLog2JsonVo>> getCatelog2JSONWithRedis(){
        String key="getCatelog2JSON";
        String json = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isEmpty(json)){
//            getCatelog2JSONForDBWithRedisLock();//redis分布式锁：源码
            getCatelog2JSONForDBWithRedisson();//redis分布式锁： redisson【再优化，使用Redisson的分布式锁】
        }

        System.err.println("缓存中已存在...");
        return JSONObject.parseObject(json, new TypeReference<>() {});
    }

    public Map<String, List<CateLog2JsonVo>> getCatelog2JSONForDBWithRedisLock() {
        String keyRedis="getCatelog2JSON";
        //加锁
        String lockKey="getCatelog2JSONForDBWithRedisLock_lock";
        String uuid=UUID.randomUUID().toString();
        //加锁需要过期时间和设置value值，value是为了判断删除的是否属于自己的锁，过期时间是为了防止发生异常出现死锁。
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, uuid,30,TimeUnit.SECONDS);
        Map<String, List<CateLog2JsonVo>> stringListMap=null;
        if (lock){
            //加锁成功 从数据库获取数据
//            stringListMap = getStringListMap(keyRedis);
            //从数据库获取数据后，释放锁
//            //查询key
//            String lock1 = stringRedisTemplate.opsForValue().get("getCatelog2JSONForDBWithRedisLock_lock");
//            //只能删除自己的锁
//            if (uuid.equals(lock1)){
//                stringRedisTemplate.delete("lock");
//            }
            try {
                System.err.println("获取分布式锁成功...");
                stringListMap = getStringListMap(keyRedis);
            }finally {
                System.err.println("开始查询删除...");
                //要查询和删除属于原子性操作
                String script="if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                //通过Redis的lua脚本实现 查询和删除操作的原子性
                stringRedisTemplate.execute(new DefaultRedisScript<Long>(script,Long.class),Arrays.asList(lockKey),uuid);
                System.err.println("查询删除成功...");
            }
            return stringListMap;
        }else {
            try {
                System.out.println("获取分布式锁失败...");
                //加锁失败，休眠+重试
                Thread.sleep(5);
                getCatelog2JSONForDBWithRedisLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return stringListMap;

    }

    private Map<String, List<CateLog2JsonVo>> getStringListMap(String keyRedis) {
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        //必须先查一遍缓存是否存在
        String json = ops.get(keyRedis);
        if (StringUtils.isNotEmpty(json)){
            System.err.println("----->缓存命中1");
            return JSONObject.parseObject(json, new TypeReference<>() {});
        }

        System.err.println("----->查询数据库");
        List<CategoryEntity> categoryEntitiesAll = this.baseMapper.selectList(new QueryWrapper<CategoryEntity>());
        List<CategoryEntity> firstCateGory = getListCategoryEntity(categoryEntitiesAll,0l);
        Map<String, List<CateLog2JsonVo>> map = firstCateGory.stream().collect(Collectors.toMap(key -> key.getCatId().toString(), value -> {

            List<CategoryEntity> categoryEntities = getListCategoryEntity(categoryEntitiesAll,value.getCatId());
            List<CateLog2JsonVo> cateLog2JsonVos = categoryEntities.stream().map(item -> {
                CateLog2JsonVo cateLog2JsonVo = new CateLog2JsonVo();
                cateLog2JsonVo.setCatalog1Id(item.getParentCid().toString());
                cateLog2JsonVo.setId(item.getCatId().toString());
                cateLog2JsonVo.setName(item.getName());

                List<CategoryEntity> categoryEntities1 = getListCategoryEntity(categoryEntitiesAll,item.getCatId());
                List<CateLog2JsonVo.Catalog3List> catalog3Lists = categoryEntities1.stream().map(item1 -> {
                    CateLog2JsonVo.Catalog3List catalog3List = new CateLog2JsonVo.Catalog3List();
                    catalog3List.setCatalog2Id(item1.getParentCid().toString());
                    catalog3List.setId(item1.getCatId().toString());
                    catalog3List.setName(item1.getName());
                    return catalog3List;
                }).collect(Collectors.toList());
                cateLog2JsonVo.setCatalog3List(catalog3Lists);

                return cateLog2JsonVo;
            }).collect(Collectors.toList());

            return cateLog2JsonVos;
        }));
        //防止缓存穿透
        if (map.isEmpty()){
            System.err.println("----->预防缓存穿透");
            //null值和"" 不允许，只能是{} *
            ops.set(keyRedis,"{}",5, TimeUnit.SECONDS);//5s过期
        }else {

            json  = JSON.toJSONString(map);
//                ops.set(key,json);
            //防止雪崩
            ops.set(keyRedis,json,new Random().nextInt(10),TimeUnit.HOURS);
//                    ops.set(key,json,10,TimeUnit.MINUTES);
        }
        return map;
    }

    //再优化，使用Redisson的分布式锁
    public Map<String, List<CateLog2JsonVo>> getCatelog2JSONForDBWithRedisson() {
        String keyRedis = "getCatelog2JSON";
        //获取分布式锁成功 加锁的时候，锁的名称需要注意
        RLock lock = redissonClient.getLock("getCatelog2JSONForDBWithRedisson_lock");
        Map<String, List<CateLog2JsonVo>> stringListMap =null;
        try{
            lock.lock();
            //加锁成功
            stringListMap = getStringListMap(keyRedis);
        }finally {
            //锁的释放
            lock.unlock();
        }
        return stringListMap;

    }

    /**
     * 再优化:使用SpringCache
     * 查询所有分类数据,并且完成一级二级三级的关联
     * @return
     */
    @Cacheable(value = {"category"},key = "{#root.methodName}",sync = true)
    @Override
    public Map<String, List<CateLog2JsonVo>> getCatelog2JSON(){

        System.err.println("----->查询数据库");
        List<CategoryEntity> categoryEntitiesAll = this.baseMapper.selectList(new QueryWrapper<CategoryEntity>());
        List<CategoryEntity> firstCateGory = getListCategoryEntity(categoryEntitiesAll,0l);
        Map<String, List<CateLog2JsonVo>> map = firstCateGory.stream().collect(Collectors.toMap(key -> key.getCatId().toString(), value -> {

            List<CategoryEntity> categoryEntities = getListCategoryEntity(categoryEntitiesAll,value.getCatId());
            List<CateLog2JsonVo> cateLog2JsonVos = categoryEntities.stream().map(item -> {
                CateLog2JsonVo cateLog2JsonVo = new CateLog2JsonVo();
                cateLog2JsonVo.setCatalog1Id(item.getParentCid().toString());
                cateLog2JsonVo.setId(item.getCatId().toString());
                cateLog2JsonVo.setName(item.getName());

                List<CategoryEntity> categoryEntities1 = getListCategoryEntity(categoryEntitiesAll,item.getCatId());
                List<CateLog2JsonVo.Catalog3List> catalog3Lists = categoryEntities1.stream().map(item1 -> {
                    CateLog2JsonVo.Catalog3List catalog3List = new CateLog2JsonVo.Catalog3List();
                    catalog3List.setCatalog2Id(item1.getParentCid().toString());
                    catalog3List.setId(item1.getCatId().toString());
                    catalog3List.setName(item1.getName());
                    return catalog3List;
                }).collect(Collectors.toList());
                cateLog2JsonVo.setCatalog3List(catalog3Lists);

                return cateLog2JsonVo;
            }).collect(Collectors.toList());

            return cateLog2JsonVos;
        }));
        return map;
    }


   private List<Long> getCatIdAll(Long catelogId, List<Long> longs){
        longs.add(catelogId);
        CategoryEntity categoryEntity = baseMapper.selectById(catelogId);
        if (categoryEntity.getParentCid()!=0){
            getCatIdAll(categoryEntity.getParentCid(),longs);
        }
        return longs;
    }


    /**
     * 根据大类找到子类
     *
     * @param category
     * @param list
     * @return
     */
    private List<CategoryEntity> getCategoryChildrens(CategoryEntity category, List<CategoryEntity> list) {

        List<CategoryEntity> collect = list.stream().filter(entity -> {
//            return category.getCatId() == entity.getParentCid();
            //大于127的数据不能用等号比较
            return (category.getCatId()).equals(entity.getParentCid());
        }).map(entity -> {
            entity.setChildrens(getCategoryChildrens(entity, list));
            return entity;
        }).sorted((e1, e2) -> {
            return (e1.getSort() == null ? 0 : e1.getSort()) - (e2.getSort() == null ? 0 : e2.getSort());
        }).collect(Collectors.toList());

        return collect;
    }

}